package cn.xeblog.plugin.game.ngsnake.ui;

import cn.xeblog.plugin.game.ngsnake.model.Direction;
import cn.xeblog.plugin.game.ngsnake.model.GameMode;
import cn.xeblog.plugin.game.ngsnake.model.Pill;
import cn.xeblog.plugin.game.ngsnake.model.Point;
import cn.xeblog.plugin.game.ngsnake.model.Snake;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
import lombok.Setter;

/**
 * 贪吃蛇游戏UI
 *
 * @author anlingyi
 * @date 2022/8/2 3:51 PM
 */
public class SnakeGameUI extends JPanel implements ActionListener {

  /**
   * 是否允许穿墙
   */
  @Setter
  private boolean pierced;

  /**
   * 宽度
   */
  private int width;

  /**
   * 高度
   */
  private int height;

  /**
   * 游戏模式
   */
  private GameMode gameMode;


  /**
   * 蛇
   */
  private Snake snake;

  /**
   * 药丸
   */
  private Pill pill;

  /**
   * 移动方向
   */
  private Direction direction;

  /**
   * 停止游戏标记
   */
  private boolean stop;

  /**
   * 游戏状态 0.初始化 1.游戏胜利 2.游戏失败
   */
  private int state = -1;

  /**
   * 定时器
   */
  private Timer timer;

  /**
   * 移动速度
   */
  private int speed = 100;

  /**
   * 分数
   */
  private int score;

  /**
   * 特殊药丸列表
   */
  private ArrayList<Pill.PillType> specialPill;

  /**
   * 构造
   *
   * @param width    宽度
   * @param height   高度
   * @param gameMode 游戏模式
   */
  public SnakeGameUI(int width, int height, GameMode gameMode) {
    this.width = width;
    this.height = height;
    this.gameMode = gameMode;
    this.timer = new Timer(speed, this);
    this.stop = true;

    // 初始化面板
    initPanel();
  }

  /**
   * 初始化
   */
  private void init() {
    // 初始化值
    this.score = 0;
    this.state = 0;
    this.stop = true;
    this.timer.setDelay(speed);

    // 初始化蛇
    initSnake();
    // 初始化药丸
    initPill();
    // 生成药丸
    generatePill();
    // 刷新界面
    super.repaint();
  }

  /**
   * 初始化游戏面板
   */
  private void initPanel() {
    SnakeGameUI snakeGameUI = this;
    // 设置此组件的首选大小
    this.setPreferredSize(new Dimension(this.width, this.height));
    // 添加键盘监听
    this.addKeyListener(getKeyListener());
    // 添加鼠标监听
    this.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        // 聚焦
        snakeGameUI.requestFocusInWindow();
      }
    });
  }

  /**
   * 获取按键事件监听
   *
   * @return 键盘监听
   */
  private KeyListener getKeyListener() {
    return new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        // 如果已经停止了且不为空格，跳过
        if (stop && e.getKeyCode() != KeyEvent.VK_SPACE) {
          return;
        }

        switch (e.getKeyCode()) {
          case KeyEvent.VK_UP:
            // 如果当前是下，跳过，即下不能变成上
            if (direction == Direction.DOWN) {
              break;
            }

            // 设置方向
            direction = Direction.UP;
            break;
          case KeyEvent.VK_DOWN:
            // 如果当前是上，跳过，即上不能变成下
            if (direction == Direction.UP) {
              break;
            }
            // 设置方向
            direction = Direction.DOWN;
            break;
          case KeyEvent.VK_LEFT:
            // 如果当前是右，跳过，即右不能变成左
            if (direction == Direction.RIGHT) {
              break;
            }
            // 设置方向
            direction = Direction.LEFT;
            break;
          case KeyEvent.VK_RIGHT:
            // 如果当前是左，跳过，即右不能变成右
            if (direction == Direction.LEFT) {
              break;
            }
            // 设置方向
            direction = Direction.RIGHT;
            break;
          case KeyEvent.VK_SPACE:
            // 只要不是初始化状态，则初始化
            if (state != 0) {
              init();
            }

            // 停止或者开始
            stop = !stop;
            if (!stop) {
              timer.start();
            }
            break;
        }
      }
    };
  }

  /**
   * 初始化蛇
   */
  private void initSnake() {
    // 默认方向
    this.direction = Direction.LEFT;
    // 最大的横坐标
    int maxX = this.width / Snake.width;
    // 最大的纵坐标
    int maxY = this.height / Snake.height;

    // 添加身体，默认模式为四个长度的小蛇
    this.snake = new Snake(new ArrayList<>());
    this.snake.add(maxX - 2, 3);
    this.snake.add(maxX - 1, 3);
    this.snake.add(maxX - 1, 2);
    this.snake.add(maxX - 1, 1);

    // 不贪吃模式
    if (gameMode == GameMode.NON_GLUTTONOUS) {
      // 第一行的填满
      for (int i = maxX - 1; i > 0; i--) {
        this.snake.add(i, 1);
      }
      // 第一列填满
      for (int i = 1; i < maxY - 1; i++) {
        this.snake.add(1, i);
      }
      // 最后一列填满
      for (int i = 1; i < maxX - 1; i++) {
        this.snake.add(i, maxY - 2);
      }
    }
  }

  /**
   * 初始化药丸
   */
  private void initPill() {
    this.specialPill = new ArrayList<>();
    // 添加5个红色药丸
    for (int i = 0; i < 5; i++) {
      this.specialPill.add(Pill.PillType.RED);
    }
    // 添加10个红色蓝丸
    for (int i = 0; i < 10; i++) {
      this.specialPill.add(Pill.PillType.BLUE);
    }
    // 随机排序一次
    Collections.shuffle(specialPill);
  }

  /**
   * 生成药丸
   */
  private void generatePill() {
    // 是否获取特殊药丸
    boolean getSpecialPill = new Random().nextInt(6) == 3;
    Pill.PillType pillType;
    if (getSpecialPill && this.specialPill.size() > 0) {
      // 生成特殊药丸
      int index = new Random().nextInt(this.specialPill.size());
      pillType = this.specialPill.get(index);
      this.specialPill.remove(index);
    } else {
      // 生成绿色药丸
      pillType = Pill.PillType.GREEN;
    }

    int x, y;

    // 是否在蛇身体里面，true,则重新生成
    boolean inSnakeBody;
    do {
      inSnakeBody = false;
      // 随机坐标
      x = new Random().nextInt(this.width / Pill.width);
      y = new Random().nextInt(this.height / Pill.height);

      // 需要跳过蛇身坐标
      int sx = x * Snake.width;
      int sy = y * Snake.height;
      if (this.snake.body.stream().anyMatch(point -> point.x == sx && point.y == sy)) {
        inSnakeBody = true;
      }
    } while (inSnakeBody);

    // 生成药丸
    this.pill = new Pill(x, y, pillType);
  }

  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    Graphics2D g2 = (Graphics2D) g;
    // 消除画图锯齿
    g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    g2.setColor(new Color(66, 66, 66));
    g2.fillRect(0, 0, this.width, this.height);

    // 画蛇
    if (this.snake != null) {
      g2.setColor(new Color(255, 255, 255));
      for (int i = this.snake.size() - 1; i >= 0; i--) {
        cn.xeblog.plugin.game.ngsnake.model.Point point = this.snake.body.get(i);
        if (i == 0) {
          // 蛇头颜色
          g2.setColor(new Color(255, 92, 92));
        } else {
          // 蛇身颜色
          g2.setColor(new Color(215, 173, 173));
        }
        // 填充矩形
        g2.fillRect(point.x, point.y, Snake.width, Snake.height);
      }
    }

    if (this.pill != null) {
      // 画药丸
      Color pillColor;
      // 按类型区分颜色
      switch (this.pill.pillType) {
        case RED:
          pillColor = new Color(255, 41, 41);
          break;
        case BLUE:
          pillColor = new Color(20, 250, 243);
          break;
        default:
          pillColor = new Color(97, 255, 113);
          break;
      }

      g2.setColor(pillColor);
      // 填椭形
      g2.fillOval(pill.point.x, pill.point.y, Pill.width, Pill.height);
    }

    // 游戏结束
    if (state > 0) {
      // 显示游戏结果
      String tips = "游戏失败！";
      if (state == 1) {
        tips = "游戏胜利！";
      }
      // 设置"提示"字体颜色
      g2.setFont(new Font("", Font.BOLD, 20));
      g2.setColor(new Color(208, 74, 74));
      g2.drawString(tips, this.width / 3, this.height / 3);

      // 设置"得分"字体颜色
      g2.setFont(new Font("", Font.PLAIN, 18));
      g2.setColor(Color.WHITE);
      g2.drawString("（" + gameMode.getName() + "）得分：" + this.score, this.width / 4, this.height / 3 + 50);
    }

    // 暂停
    if (stop) {
      g2.setFont(new Font("", Font.PLAIN, 18));
      g2.setColor(Color.WHITE);
      g2.drawString("按空格键开始/暂停游戏！", this.width / 4, this.height - 50);
    }
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    // 是否吃药
    boolean isAte = false;
    if (!this.stop) {
      // 移动蛇
      this.snake.move(this.direction);
      Point head = this.snake.getHead();

      // 判断蛇有没有咬到自己或是撞墙
      int maxWidth = this.width - this.snake.width;
      int maxHeight = this.height - this.snake.height;
      // 是否撞墙
      boolean isHitWall = head.x > maxWidth || head.x < 0 || head.y > maxHeight || head.y < 0;

      // 撞墙且允许撞墙，即穿墙
      if (isHitWall && pierced) {
        // 从最后一列到第一列
        if (head.x > maxWidth) {
          head.x = 0;
        }
        // 从第一列到最后一列来
        if (head.x < 0) {
          head.x = maxWidth;
        }
        // 从最后一行到第一行
        if (head.y > maxHeight) {
          head.y = 0;
        }
        // 从第一行到最后一行
        if (head.y < 0) {
          head.y = maxHeight;
        }
      }

      // 是否被咬
      boolean isBiting = false;

      for (int i = this.snake.size() - 1; i > 0; i--) {
        // 判断当前头有没有碰到身体
        if (head.equals(this.snake.body.get(i))) {
          isBiting = true;
          break;
        }
      }
      // 被咬或者不允许穿墙且碰墙，游戏失败
      if (isBiting || (!pierced && isHitWall)) {
        this.state = 2;
        this.stop = true;
      }

      // 吃药了
      if (head.equals(this.pill.point)) {
        // 已吃
        isAte = true;
        // 药丸分数
        int getScore = this.pill.pillType.score;
        // 累计分数
        this.score += getScore;

        // 不贪吃模式
        if (gameMode == GameMode.NON_GLUTTONOUS) {
          for (int i = 0; i < getScore; i++) {
            // 移除蛇尾
            this.snake.removeLast();
            if (this.snake.size() == 0) {
              // 游戏胜利
              this.state = 1;
              this.stop = true;
              break;
            }
          }
        } else {
          // 蛇身长度+1
          Point tail = this.snake.getTail();
          this.snake.add(tail.x, tail.y);
        }

        // 药丸置空
        pill = null;
        if (this.score % 10 == 0) {
          int curSpeed = this.timer.getDelay();
          if (curSpeed > 30) {
            // 加速
            this.timer.setDelay(curSpeed - 10);
          }
        }
      }
    }

    // 暂停
    if (this.stop) {
      this.timer.stop();
    } else if (isAte) {
      // 重新生成药丸
      generatePill();
    }
    // 刷新界面
    repaint();
  }

}
